package com.itheima.project.security;

import com.alibaba.fastjson.JSONObject;
import com.itheima.project.constant.SuperConstant;
import com.itheima.project.constant.security.OauthCacheConstant;
import com.itheima.project.constant.security.OauthConstant;
import com.itheima.project.utils.EmptyUtil;
import com.itheima.project.vo.security.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * @ClassName AuthorizationManager.java
 * @Description 鉴权管理器
 */
@Slf4j
@Component
public class JwtReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    ReactiveJwtDecoder reactiveJwtDecoder;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication,
                                             AuthorizationContext authorizationContext) {
        //拦截获得url路径
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        String path = request.getURI().getPath();
        //支持restfull
        String methodValue = request.getMethodValue();
        String tagetResource = methodValue+path;
        log.info("===============进入鉴权tagetResource路径:{}==========",tagetResource);
        //userToken令牌校验,如果当前的tooken获得为空，则认为鉴权失败
        String userToken = request.getHeaders().getFirst(SuperConstant.USER_TOKEN);
        if (EmptyUtil.isNullOrEmpty(userToken)){
            return Mono.justOrEmpty(new AuthorizationDecision(false));
        }
        //使用当前usertoken获得缓存中用户信息
        RBucket<UserVo> jtiUserBucket = redissonClient.getBucket(OauthCacheConstant.JTI_USER + userToken);
        UserVo userVo = jtiUserBucket.get();
        if (EmptyUtil.isNullOrEmpty(userVo)){
            return Mono.justOrEmpty(new AuthorizationDecision(false));
        }
        String jti = userVo.getJti();
        //使用usernam兑换当前用户绑定的jti，如果页面传入jti不等于缓存中的jti,则表示当前jti被剔除
        RBucket<String> usernameBucket = redissonClient.getBucket(OauthCacheConstant.BIND_USERNAME + userVo.getUsername());
        String bindJti = usernameBucket.get();
        if (!jti.equals(bindJti)){
            return Mono.justOrEmpty(new AuthorizationDecision(false));
        }
        //使用jti兑换去缓存中兑换accessToken
        RBucket<String> accessTokenBucket = redissonClient.getBucket(OauthCacheConstant.ACCESS_TOKEN + jti);
        String accessToken = accessTokenBucket.get();
        //令牌过期获得为空，校验失败
        if (EmptyUtil.isNullOrEmpty(accessToken)){
            return Mono.justOrEmpty(new AuthorizationDecision(false));
        }
        //解析accessToken为jwt对象，且调用rsa接口进行验证签名
        Jwt jwt = reactiveJwtDecoder.decode(accessToken).block();
        //判断当前用户是否拥有权限
        List<String> authorities = (List<String>)jwt.getClaims().get(OauthConstant.RESOURCS_KEY);
        for (String authority : authorities) {
            boolean isMatch = antPathMatcher.match(authority, tagetResource);
            if (isMatch){
                log.info("用户:{}拥有tagetResource权限:{}==========",userVo.getUsername(),tagetResource);
                return Mono.just(new AuthorizationDecision(true));
            }
        }
        log.info("用户:{}不拥有tagetResource权限:{}==========",userVo.getUsername(),tagetResource);
        return Mono.just(new AuthorizationDecision(false));
    }

}
